Timezone Generic Clock
/**************************************************************
  TZ_NTP_Clock_WiFi.ino
  
  For AVR, ESP8266/ESP32, SAMD21/SAMD51, nRF52, STM32, WT32_ETH01 boards
  Based on and modified from Arduino Timezone Library (https://github.com/JChristensen/Timezone)
  to support other boards such as ESP8266/ESP32, SAMD21, SAMD51, Adafruit's nRF52 boards, etc.
  Copyright (C) 2018 by Jack Christensen and licensed under GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
  Built by Khoi Hoang https://github.com/khoih-prog/Timezone_Generic
  Licensed under MIT license
***************************************************************************/
#include "defines.h"
// included only in main(), .ino with setup() to avoid `Multiple Definitions` Error
#include  // https://github.com/khoih-prog/Timezone_Generic

//************************* DEFINES ************************************
#define TIMEZONE_GENERIC_VERSION_MIN_TARGET      "Timezone_Generic v1.10.1"
#define TIMEZONE_GENERIC_VERSION_MIN             1010001
#define USING_INITIALIZED_TZ      false   //true

//************************* PROTOTYPES ************************************

//************************* VARIABLES ************************************
#if USING_INITIALIZED_TZ
  // US Eastern Time Zone (New York, Detroit,Toronto)
  TimeChangeRule myDST = {"EDT", Second, Sun, Mar, 2, -240};    // Daylight time = UTC - 4 hours
  TimeChangeRule mySTD = {"EST", First,  Sun, Nov, 2, -300};    // Standard time = UTC - 5 hours
  Timezone *myTZ;
#else
  // Allow a "blank" TZ object then use begin() method to set actual TZ.
  // Feature added by 6v6gt (https://forum.arduino.cc/index.php?topic=711259)
  Timezone *myTZ;
  TimeChangeRule myDST;
  TimeChangeRule mySTD;
#endif

// If TimeChangeRules are already stored in EEPROM, comment out three
// lines above and uncomment line below.
//Timezone myTZ( 100 );       // assumes rules stored at EEPROM address 100
TimeChangeRule *tcr;  // pointer to time change rule, use to get TZ abbrev
int status = WL_IDLE_STATUS;      // Wifi radio's status
char timeServer[]         = "time.nist.gov";  // NTP server
unsigned int localPort    = 2390;     // local port to listen for UDP packets
const int NTP_PACKET_SIZE = 48;       // NTP timestamp is in first 48 bytes of message
const int UDP_TIMEOUT     = 2000;     // timeout in miliseconds to wait for an UDP packet to arrive
byte PktBuff[NTP_PACKET_SIZE];   // buffer to hold incoming and outgoing packets
// A UDP instance to let us send and receive packets over UDP
WiFiUDP Udp;

/*F********************************************************************
*
**********************************************************************/
void 
setup()
{
	Serial.begin( BAUD );
	while( !Serial && millis() < 5000);
	delay( 200 );
	Serial.print( F("\nStart TZ_NTP_Clock_WiFi on ")); 
	Serial.println( BOARD_NAME );
	Serial.println( TIMEZONE_GENERIC_VERSION );
#if defined( TIMEZONE_GENERIC_VERSION_MIN )
	if( TIMEZONE_GENERIC_VERSION_INT < TIMEZONE_GENERIC_VERSION_MIN)
	{
		Serial.print( "Warning. Must use this example on Version equal "
			"or later than : ");
		Serial.println( TIMEZONE_GENERIC_VERSION_MIN_TARGET);
	}
#endif
	if( WiFi.status() == WL_NO_SHIELD)       // CHECK FOR PRESENCE OF SHIELD
	{
		Serial.println( F( "WiFi shield not present"));
		while( true );                                     // DON'T CONTINUE
	}
	while( status != WL_CONNECTED)     // ATTEMPT TO CONNECT TO WiFi NETWORK
	{
		Serial.print( F("Connecting to WPA SSID: "));
		Serial.println( ssid);
		status = WiFi.begin( ssid, pass );    // CONNECT TO WPA/WPA2 NETWORK
	}
	// YOU'RE CONNECTED NOW, SO PRINT OUT DATA
	Serial.print( F( "You're connected to network, IP = "));
	Serial.println( WiFi.localIP() );
#if( USING_INITIALIZED_TZ )
		myTZ = new Timezone( myDST, mySTD );
#else
	String tzName = "EDT/EST" ; // CAN READ THIS INFO FROM EEPROM, STORAGE, ETC
	// TIME ZONE RULES CAN BE SET AS BELOW OR DYNAMICALLY BUILT, SAY THROUGH A CONFIGURATION
	if(  tzName == "EDT/EST" ) // INTERFACE, OR FETCHED FROM EEPROM, FLASH ETC
	{
		// AMERICA EASTERN TIME
		myDST = (TimeChangeRule) {"EDT", Second, Sun, Mar, 2, -240};// DST time = UTC - 4 hours
		mySTD = (TimeChangeRule) {"EST", First, Sun, Nov, 2, -300};// STD = UTC - 5 hours
	}
	else if(  tzName == "CET/CEST" ) 
	{
		// central Europe
		myDST = (TimeChangeRule) {"CEST", Last, Sun, Mar, 2, 120};
		mySTD = (TimeChangeRule) {"CET",  Last, Sun, Oct, 3, 60};
	}
	else if(  tzName == "GMT/BST" ) 
	{ // UK
		myDST = (TimeChangeRule) {"BST",  Last, Sun, Mar, 1, 60};
		mySTD = (TimeChangeRule) {"GMT",  Last, Sun, Oct, 2, 0};
	}
	myTZ = new Timezone();
	myTZ->init( myDST, mySTD );
#endif
	myTZ->writeRules();
	Udp.begin(localPort);
	Serial.print(F("Listening on port "));
	Serial.println(localPort);
}
/*F********************************************************************
*
**********************************************************************/
void 
loop()
{
	getNTPTime();
	displayClock();
}
/*F********************************************************************
* send an NTP request to time server at given address
**********************************************************************/
void 
sendNTPpacket( char *ntpSrv)
{
	// set all bytes in buffer to 0
	memset( PktBuff, 0, NTP_PACKET_SIZE );
	// Initialize values needed to form NTP request
	// (see URL above for details on packets)
	PktBuff[0] = 0b11100011;   // LI, Version, Mode
	PktBuff[1] = 0;            // Stratum, or type of clock
	PktBuff[2] = 6;            // Polling Interval
	PktBuff[3] = 0xEC;         // Peer Clock Precision
	// 8 bytes of zero for Root Delay & Root Dispersion
	PktBuff[12]  = 49;
	PktBuff[13]  = 0x4E;
	PktBuff[14]  = 49;
	PktBuff[15]  = 52;
	// all NTP fields have been given values, now
	// you can send a packet requesting a timestamp:
	Udp.beginPacket(ntpSrv, 123); //NTP requests are to port 123
	Udp.write(PktBuff, NTP_PACKET_SIZE);
	Udp.endPacket();
}
/*F********************************************************************
* format and print a time_t value, with a time zone appended.
**********************************************************************/
void 
printDateTime( time_t t, const char *tz)
{
	char buf[32];
	char m[4];    // temp month string stg (DateStrings.cpp uses shared buffer)
	strcpy( m, monthShortStr(month(t)));
	sprintf( buf, "%.2d:%.2d:%.2d %s %.2d %s %d %s", hour(t), minute(t)
		, second(t), dayShortStr(weekday(t)), day(t), m, year(t), tz);
	Serial.println( buf );
}
/*F********************************************************************
*
**********************************************************************/
void 
displayClock( void )
{
	time_t utc = now();
	time_t local = myTZ->toLocal(utc, &tcr);
	Serial.println();
	printDateTime(utc, "UTC");
	printDateTime(local, tcr -> abbrev);
	delay(10000);
}
/*F********************************************************************
*
**********************************************************************/
void 
getNTPTime( void )
{
	static bool gotCurrentTime = false;
	if( !gotCurrentTime )                      // JUST GET CORRECT TIME ONCE
	{
		sendNTPpacket( timeServer ); // SEND AN NTP REQUEST TO A TIME SERVER
		delay( 1000 );                // WAIT TO SEE IF A REPLY IS AVAILABLE
		if( Udp.parsePacket())
		{
			Serial.println(F("Packet received"));
			// RECEIVED A PACKET, READ DATA FROM IT
			Udp.read( PktBuff, NTP_PACKET_SIZE); 
			// TIMESTAMP STARTS AT BYTE 40 OF RECEIVED PACKET AND IS FOUR BYTES,
			// OR TWO WORDS, LONG. FIRST, EXTRACT TWO WORDS:
			unsigned long highWord = word( PktBuff[40], PktBuff[41]);
			unsigned long lowWord = word( PktBuff[42], PktBuff[43]);
			// COMBINE FOUR BYTES (TWO WORDS) INTO A LONG INT
			// THIS IS NTP TIME (SECONDS SINCE JAN 1 1900):
			unsigned long secsSince1900 = highWord << 16 | lowWord;
			Serial.print( F( "Seconds since Jan 1 1900 = "));
			Serial.println(secsSince1900);
			// NOW CONVERT NTP TIME INTO EVERYDAY TIME
			Serial.print( F( "Unix time = "));
			// UNIX TIME STARTS ON jAN 1 1970. iN SECONDS, THAT'S 2208988800:
			const unsigned long seventyYears = 2208988800UL;
			// SUBTRACT SEVENTY YEARS:
			unsigned long epoch = secsSince1900 - seventyYears;
			time_t epoch_t = epoch;                       // GET EPOCH TIME_T 
			// SET SYSTEM TIME TO UTC
			// WARNING: ASSUMES THAT COMPILEtIME() RETURNS US EDT
			// ADJUST FOLLOWING LINE ACCORDINGLY IF YOU'RE IN ANOTHER TIME ZONE
			setTime( epoch_t );
			// PRINT Unix TIME:
			Serial.println( epoch );
			// PRINT HOUR, MINUTE AND SECOND:
			Serial.print( F( "The UTC time is "));             // UTC TIME
			Serial.print( (epoch  % 86400L) / 3600); // PRINT HOUR (86400 == SECS / DAY)
			Serial.print( ':' );
			if( ((epoch % 3600) / 60) < 10)
				Serial.print( '0' ); // 1st 10 MIN OF HOUR, NEED LEADING '0'
			Serial.print( (epoch  % 3600) / 60); // PRINT MINUTE (3600 == SECS / MIN)
			Serial.print(':');
			if( (epoch % 60) < 10)
				Serial.print( '0' ); // IN 1ST 10 SECS OF EACH MIN NEED LDNG '0'
			Serial.println( epoch % 60 );                    // PRINT SECOND
			gotCurrentTime = true;
		}
		else
			delay( 10000 ); // WAIT TEN SECONDS BEFORE ASKING FOR TIME AGAIN
	}
}